package com.nidu.demo.redis.lock;

import com.nidu.demo.redis.config.RedisProperties;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;

import java.util.Collections;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * Redis分布式锁实现
 *
 * @author nidu
 */
@Slf4j
@Component
public class RedisDistributedLock {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Autowired
    private RedisProperties redisProperties;

    /**
     * 解锁Lua脚本
     */
    private static final String UNLOCK_SCRIPT = 
            "if redis.call('get', KEYS[1]) == ARGV[1] then " +
            "  return redis.call('del', KEYS[1]) " +
            "else " +
            "  return 0 " +
            "end";

    private final DefaultRedisScript<Long> unlockScript;

    public RedisDistributedLock() {
        this.unlockScript = new DefaultRedisScript<>();
        this.unlockScript.setScriptText(UNLOCK_SCRIPT);
        this.unlockScript.setResultType(Long.class);
    }

    /**
     * 尝试获取锁
     *
     * @param lockKey 锁的键
     * @return 锁的值（用于释放锁）
     */
    public String tryLock(String lockKey) {
        return tryLock(lockKey, redisProperties.getLock().getDefaultExpireTime(), TimeUnit.MILLISECONDS);
    }

    /**
     * 尝试获取锁（带过期时间）
     *
     * @param lockKey    锁的键
     * @param expireTime 过期时间
     * @param timeUnit   时间单位
     * @return 锁的值（用于释放锁）
     */
    public String tryLock(String lockKey, long expireTime, TimeUnit timeUnit) {
        String lockValue = UUID.randomUUID().toString();
        String fullKey = buildLockKey(lockKey);
        
        Boolean success = stringRedisTemplate.opsForValue()
                .setIfAbsent(fullKey, lockValue, expireTime, timeUnit);
        
        if (Boolean.TRUE.equals(success)) {
            log.debug("[RedisLock] 获取锁成功: key={}, value={}, expireTime={}, unit={}", 
                    fullKey, lockValue, expireTime, timeUnit);
            return lockValue;
        } else {
            log.debug("[RedisLock] 获取锁失败: key={}", fullKey);
            return null;
        }
    }

    /**
     * 尝试获取锁（带重试）
     *
     * @param lockKey 锁的键
     * @return 锁的值（用于释放锁）
     */
    public String tryLockWithRetry(String lockKey) {
        return tryLockWithRetry(lockKey, 
                redisProperties.getLock().getDefaultExpireTime(), TimeUnit.MILLISECONDS,
                redisProperties.getLock().getRetryCount(), 
                redisProperties.getLock().getRetryInterval());
    }

    /**
     * 尝试获取锁（带重试）
     *
     * @param lockKey       锁的键
     * @param expireTime    过期时间
     * @param timeUnit      时间单位
     * @param retryCount    重试次数
     * @param retryInterval 重试间隔（毫秒）
     * @return 锁的值（用于释放锁）
     */
    public String tryLockWithRetry(String lockKey, long expireTime, TimeUnit timeUnit, 
                                   int retryCount, long retryInterval) {
        for (int i = 0; i <= retryCount; i++) {
            String lockValue = tryLock(lockKey, expireTime, timeUnit);
            if (lockValue != null) {
                return lockValue;
            }
            
            if (i < retryCount) {
                try {
                    Thread.sleep(retryInterval);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    log.warn("[RedisLock] 获取锁时被中断: key={}", lockKey);
                    return null;
                }
            }
        }
        
        log.warn("[RedisLock] 重试获取锁失败: key={}, retryCount={}", lockKey, retryCount);
        return null;
    }

    /**
     * 释放锁
     *
     * @param lockKey   锁的键
     * @param lockValue 锁的值
     * @return 是否释放成功
     */
    public boolean unlock(String lockKey, String lockValue) {
        if (lockValue == null) {
            return false;
        }
        
        String fullKey = buildLockKey(lockKey);
        Long result = stringRedisTemplate.execute(unlockScript, 
                Collections.singletonList(fullKey), lockValue);
        
        boolean success = Long.valueOf(1).equals(result);
        log.debug("[RedisLock] 释放锁: key={}, value={}, success={}", 
                fullKey, lockValue, success);
        
        return success;
    }

    /**
     * 检查锁是否存在
     *
     * @param lockKey 锁的键
     * @return 是否存在
     */
    public boolean isLocked(String lockKey) {
        String fullKey = buildLockKey(lockKey);
        Boolean exists = stringRedisTemplate.hasKey(fullKey);
        return Boolean.TRUE.equals(exists);
    }

    /**
     * 构建完整的锁键
     */
    private String buildLockKey(String lockKey) {
        return redisProperties.getKeyPrefix() + redisProperties.getLock().getKeyPrefix() + lockKey;
    }

    /**
     * 锁的自动管理类
     */
    public static class LockHandle implements AutoCloseable {
        private final RedisDistributedLock lock;
        private final String lockKey;
        private final String lockValue;

        public LockHandle(RedisDistributedLock lock, String lockKey, String lockValue) {
            this.lock = lock;
            this.lockKey = lockKey;
            this.lockValue = lockValue;
        }

        @Override
        public void close() {
            if (lockValue != null) {
                lock.unlock(lockKey, lockValue);
            }
        }

        public boolean isLocked() {
            return lockValue != null;
        }
    }

    /**
     * 获取可自动释放的锁
     *
     * @param lockKey 锁的键
     * @return 锁句柄
     */
    public LockHandle getLockHandle(String lockKey) {
        String lockValue = tryLock(lockKey);
        return new LockHandle(this, lockKey, lockValue);
    }

    /**
     * 获取可自动释放的锁（带重试）
     *
     * @param lockKey 锁的键
     * @return 锁句柄
     */
    public LockHandle getLockHandleWithRetry(String lockKey) {
        String lockValue = tryLockWithRetry(lockKey);
        return new LockHandle(this, lockKey, lockValue);
    }
}
